UPDATE v551: Part yang hanya punya NAMA tidak lagi otomatis Waiting Part
             — dicocokkan ke SOH lewat namanya

>>> BACKEND BERUBAH — WAJIB RESTART <<<
>>> FRONTEND WAJIB BUILD ULANG <<<
>>> TIDAK ADA PERUBAHAN STRUKTUR TABEL <<<

BERKAS BERUBAH (2)
  backend/routes/detailpart.js            endpoint baru + perbaikan impor
  frontend/src/views/MonitoringUnit.vue   pencocokan nama + tampilannya

CATATAN: paket ini TIDAK memuat v549/v550 (izin bypass Pengecekan Awal).
Kedua berkas di sini tidak bersinggungan dengan perubahan itu, jadi
urutan pemasangannya bebas.


================================================================
1. APA YANG SEBENARNYA TERJADI
================================================================
Seluruh pencarian stok berpatokan pada Part Number saja. Di
detailpart.js, jalur impor Excel membungkus SELURUH pengecekan gudang
dengan `if (pn) { ... }` — baris tanpa Part Number melewatinya, lalu
jatuh ke baris ini:

    const status = stockInfo ? (Stock > 0 ? 'Tersedia' : 'Waiting Part')
                             : 'Waiting Part'

`stockInfo` tetap null karena tidak pernah dicari. Jadi "TIDAK DICEK"
dan "dicek, ternyata habis" berakhir di tempat yang sama persis.

Nama part tidak pernah dilihat, padahal kolomnya ada di ketiga tabel
SOH.

Di frontend polanya sama: cekStokManual() dan cekStokBanyak() keduanya
berhenti di baris pertama bila Part Number kosong.


================================================
2. AKIBAT YANG TIDAK TERLIHAT DI LAYAR ITU
================================================
Baris tanpa Part Number tidak sekadar tampil "⏳ Waiting". Ia ikut
terhitung sebagai part yang menunggu, dan sinkronRekap() di backend
lalu:

  - menarik SELURUH record ke Status_Penggunaan = 'Waiting Part'
  - membuka segmen 'Waiting Part' di status_durasi_log

Segmen itu terus berjalan sampai ada yang menutupnya. Jadi satu baris
yang cuma diketik namanya bisa membuat unit tercatat menunggu part
berhari-hari — dan angka itu ikut terbawa ke MTTR serta rekap durasi.

DAN IA TIDAK DILAPORKAN. Daftar notFound hanya diisi saat `pn` ada,
jadi baris tanpa Part Number tidak muncul di ringkasan impor mana pun.
Satu-satunya jejaknya adalah status menunggu yang tidak dijelaskan apa
pun.


================================================
3. DUA PERBAIKAN, DAN BATASNYA
================================================
A. NAMA DICOCOKKAN KE SOH

   Endpoint baru: POST /detail-part/cek-stok-nama

   cocok PERSIS ke satu Part Number
       nama sama setelah spasi dirapikan dan huruf disamakan
       -> Part Number-nya DIISIKAN sendiri, lalu cek stok biasa jalan
       -> ditandai "✓ diisi dari nama part" di bawah kotaknya

   selain itu
       -> TIDAK ditebak. Muncul chip "🔎 N part mirip namanya — pilih".
          Ditekan, daftarnya terbuka dan orangnya memilih sendiri.

   tidak ada yang menyerupai
       -> "🔎 tidak ada yang mirip di SOH MSF 2", status dibiarkan
          kosong

B. YANG TIDAK DICEK DISIMPAN KOSONG, BUKAN 'Waiting Part'

   Status kosong tidak terhitung menunggu, jadi record-nya tidak
   tertarik dan durasi waiting tidak berjalan.

   Part Number yang ADA tapi tidak ketemu di gudang mana pun TETAP
   'Waiting Part' seperti sebelumnya — itu memang sudah dicek, dan ia
   juga sudah dilaporkan lewat daftar notFound.


================================================
4. KENAPA "COCOK SEBAGIAN YANG HANYA SATU" TETAP HARUS DIPILIH
================================================
Ini keputusan yang paling mudah salah dan paling perlu dijelaskan.

Kalau "Hose AC" hanya menyerupai SATU baris SOH, godaannya adalah
menerapkannya otomatis — toh tidak ada pilihan lain. Tapi satu-satunya
baris itu bisa saja "HOSE AC COMPRESSOR", part yang sama sekali
berbeda.

Menerapkannya berarti mengisi Part Number yang salah ke permintaan yang
akan dikirim ke gudang. Tidak ada yang akan menyadarinya sampai
barangnya datang keliru — dan saat itu jejaknya sudah hilang, karena di
layar Part Number itu terlihat seperti diketik orang.

Jadi aturannya: SATU-SATUNYA KECOCOKAN BUKAN BUKTI KECOCOKAN YANG
BENAR. Hanya kesamaan nama PERSIS yang diterapkan sendiri.

Sebaliknya, nama yang sama persis tapi menunjuk DUA Part Number berbeda
juga masuk "pilih" — itu keadaan nyata di data Anda, dan menebak salah
satunya sama buruknya.


================================================
5. PENCOCOKAN TIDAK MENGHITUNG STOK SENDIRI
================================================
Endpoint /cek-stok-nama hanya menjawab SATU hal: nama ini menunjuk Part
Number yang mana. Begitu Part Number-nya diketahui, layar memanggil
/cek-stok yang sudah ada.

Itu disengaja. Aturan stok memuat hal-hal yang tidak boleh punya
salinan kedua:

  - gudang mana yang dipakai menurut satker unit (PMSE-1 -> MSF 1)
  - lokasi otomatis
  - pembedaan "terdaftar tapi habis" dari "belum pernah didaftarkan"
    (v502)

Menyalinnya ke endpoint baru berarti dua tempat yang bisa memberi
jawaban berbeda untuk part yang sama.

Hal yang sama berlaku di layar: memilih dari daftar hasil pencocokan
menuangkannya ke _sohResults — tempat yang SAMA dengan hasil pencarian
biasa — sehingga yang dipanggil tetap pilihSoh() yang sudah ada.


================================================
6. ENAM QUERY, BUKAN SATU PER NAMA
================================================
Tiga query untuk cocok persis (IN ...), tiga untuk cocok sebagian
(LIKE ... OR LIKE ...), lalu dicocokkan di memori. Enam total, berapa
pun jumlah namanya.

Satu query per nama akan tumbuh mengikuti panjang daftar, dan paket PM
bisa belasan part.

Pencarian sebagian diberi LIMIT 300 per gudang: pola '%kata%' pada tabel
SOH yang besar bisa mengembalikan ribuan baris, dan mengirim semuanya ke
layar hanya untuk dibuang di sana adalah muatan yang tidak ada gunanya.

Nama di bawah 3 huruf dilewati. Dua huruf akan menyerupai ratusan baris,
dan daftar pilihan sepanjang itu bukan bantuan.

Baris dari tiga gudang DILEBUR per Part Number. Part yang sama ada di
MSF 1 dan MSF 2 adalah SATU pilihan, bukan dua — menampilkannya dua kali
akan terbaca seperti dua part berbeda dengan nama sama, dan itu justru
pertanyaan yang sedang dijawab.

Gudang yang sesuai satker unit ditaruh di depan saat harus memilih:
pilihan teratas adalah yang paling mungkin ditekan tanpa dibaca.


================================================
7. GAGAL MENCOCOKKAN TIDAK MEMBUATNYA WAITING
================================================
Kalau permintaan pencocokan gagal, status barisnya dibiarkan apa adanya
dan galatnya ditampilkan di baris itu.

Itu persis alasan seluruh perubahan ini ada: "tidak tahu" tidak boleh
disimpan sebagai "menunggu".


================================================
8. RINGKASAN IMPOR SEKARANG MENYEBUTNYA
================================================
Muncul bagian baru di notifikasi hasil impor:

    🔎 2 baris tanpa Part Number — dicocokkan lewat namanya
       Baris ini tidak dihitung sebagai Waiting Part, jadi status unit
       tidak ikut tertarik. Lihat kolom Part Number di tabel...

Dipisah dari daftar notFound karena perbaikannya berbeda: notFound sudah
punya Part Number dan tinggal didaftarkan ke gudang; yang ini bahkan
belum ketahuan menunjuk part yang mana.


================================================
9. DATA LAMA TIDAK DIUBAH
================================================
Baris yang terlanjur tersimpan sebagai 'Waiting Part' TIDAK dibetulkan
otomatis. Membetulkannya berarti menebak mana yang memang menunggu dan
mana yang cuma tidak pernah dicek — dan dua-duanya sekarang terlihat
sama di database.

Baris seperti itu bisa dicari dengan:

    SELECT rekap_id, Part_Name, Status_Part, Waiting_Start
      FROM detail_part_rekap
     WHERE (Part_Number IS NULL OR TRIM(Part_Number) = '')
       AND LOWER(TRIM(COALESCE(Status_Part,''))) = 'waiting part';

Membuka form record-nya lalu menghapus isi kolom statusnya akan
menutup segmen waiting-nya lewat sinkronRekap() seperti biasa. Kalau
Anda ingin saya buatkan pembersihan massalnya, bilang saja — tapi itu
menyentuh durasi yang sudah tercatat, jadi lebih baik diputuskan
tersendiri.


================================================
10. CARA PASANG
================================================
1. Extract ke folder yang MEMUAT folder Digitalisasi_3_3, lalu timpa.
2. RESTART BACKEND.
3. cd frontend && npm run build
4. Ctrl+Shift+R di peramban.

5. UJI DENGAN RECORD DI LAYAR ANDA (SE-3004):
   a. Buka Edit Kebutuhan Sparepart. Baris "Hose AC Split Low" dan
      "Clamp hose gbagong" TIDAK boleh lagi bertuliskan "⏳ Waiting".
   b. Yang namanya cocok persis: Part Number-nya terisi sendiri, dengan
      keterangan "✓ diisi dari nama part" di bawahnya.
   c. Yang mirip-mirip: muncul chip "🔎 N part mirip namanya — pilih".
      Tekan, pilih satu, stoknya langsung terisi.
   d. Yang tidak ada padanannya: "🔎 tidak ada yang mirip", status
      "— belum dicek".
   e. Status Penggunaan Unit TIDAK boleh lagi ikut jadi "Waiting Part"
      hanya karena baris-baris itu.


================================================
YANG SUDAH DIPERIKSA
================================================
- Aturan pencocokan diuji terhadap 7 keadaan, semuanya benar:
      nama sama persis, 1 PN di 2 gudang   -> persis, DITERAPKAN
                                              (dua gudang lebur jadi
                                               satu pilihan)
      huruf kecil + spasi ganda            -> persis, DITERAPKAN
      nama sama persis, PN BERBEDA         -> pilih, tidak ditebak
      cocok sebagian, banyak               -> pilih
      cocok sebagian, hanya SATU           -> pilih  <- lihat bagian 4
      tidak ada yang menyerupai            -> kosong
- Status impor diuji terhadap 4 keadaan:
      PN ada, stok > 0                     -> Tersedia
      PN ada, stok 0                       -> Waiting Part
      PN ada, tidak ada di gudang mana pun -> Waiting Part (tidak
                                              berubah, dan tetap masuk
                                              notFound)
      TANPA PN                             -> kosong  <- yang diperbaiki
- Perambatan ke status record diuji: 2 baris netral + 1 Tersedia
  menghasilkan 0 baris menunggu, jadi record TIDAK tertarik ke Waiting
  Part — dan baris netral tidak menutupi Waiting yang sungguhan.
- detailpart.js lolos `node --check`
- 40 berkas .vue lolos @vue/compiler-sfc — 0 bermasalah
- `vite build` DIJALANKAN dan BERHASIL (15,2 detik)
- Penanda hasil pencocokan diperiksa agar DIBUANG saat orangnya
  mengetik Part Number sendiri. Kalau tidak, keterangan "diisi dari
  nama part" akan menempel pada nilai yang sudah diganti tangan —
  keterangan yang salah lebih buruk daripada tidak ada keterangan.


================================================
YANG BELUM SAYA UJI
================================================
Tidak ada MySQL di tempat saya bekerja. Query pencocokan nama belum
pernah dijalankan terhadap tabel SOH sungguhan.

DUA HAL YANG PERLU DIPERHATIKAN SAAT LANGKAH 5:

  KECEPATAN. Pencarian '%kata%' TIDAK bisa memakai indeks — apa pun
  indeks yang ada di kolom Part_Name. Pada tabel SOH yang besar, query
  cocok-sebagian akan memindai seluruh tabel. Batas waktunya 20 detik,
  dan gagal tidak merusak apa pun (bagian 7), tapi kalau chip
  "🔎 cocokkan nama…" terasa lama, itu sebabnya. Kalau memang lambat,
  kabari saya — jalan keluarnya ada, tapi berbeda tergantung seberapa
  besar tabelnya.

  ISI KOLOM Part_Name di SOH. Seluruh fitur ini bersandar pada nama di
  SOH ditulis mirip dengan yang diketik orang. Kalau penulisannya jauh
  berbeda, hasilnya akan banyak "tidak ada yang mirip" — dan itu
  BUKAN kegagalan program. Bedanya sekarang: baris seperti itu tidak
  lagi diam-diam jadi Waiting Part.
